#include "wm.h"

#include "window.h"

NX_PRIVATE void WindowMergeUpdateRegion(NX_Region r, WM_WindowType * w)
{
    if (w)
    {
        if (w->flags & NX_WINDOW_FLAG_DIRECTDRAW)
        {
            WM_MergeUpdateRegion(r);
            WM_FlushWindow();
        }
        else
        {
            w->flushSync = WM_flushSync_SEND;
            WM_SendRenderEvent((WM_WindowRenderEvent){
                .type = WM_RENDER_UPDATE,
                .update_event = {
                    .region = r,
                    .wid = w->id,
                }});

            /* wait window flush done */
            NX_SemaphoreWait(w->updateSem);
        }
    }
    else
    {
        WM_SendRenderEvent((WM_WindowRenderEvent){
            .type = WM_RENDER_UPDATE,
            .update_event = {
                .region = r,
                .wid = NX_WIN_ID_INVALID,
            }});
    }
}

NX_PRIVATE NX_Error WindowInit(WM_WindowType * w, int x, int y, int width, int height, NX_Color bg, NX_U32 flags,
    NX_Solt client, NX_WindowConnect *connect)
{
    NX_Color *pixel;
    NX_Solt map_solt = NX_SOLT_INVALID_VALUE;

    if (!w || width <= 0 || height <= 0)
    {
        return NX_EINVAL;
    }

    w->event = NX_FifoCreate(sizeof(NX_WindowEvent) * WM_FIFO_EVENT_LEN);
    if (w->event < 0)
    {
        return NX_ENOMEM;
    }

    w->vmo = NX_ShareMemOpen(NX_NULL, width * height * sizeof(NX_Color), NX_SHAREMEM_ANONYMOUS);
    if (w->vmo == NX_SOLT_INVALID_VALUE)
    {
        NX_FifoDestroy(w->event);
        return NX_ENOMEM;
    }

    pixel = (NX_Color *)NX_ShareMemMap(w->vmo, &map_solt);
    if (!pixel)
    {
        NX_ShareMemClose(w->vmo);
        NX_FifoDestroy(w->event);
        return NX_ENOMEM;
    }

    if (WM_WindowAllocID(w) < 0)
    {
        NX_ShareMemUnmap(map_solt);
        NX_ShareMemClose(w->vmo);
        NX_FifoDestroy(w->event);
        return NX_ENORES;
    }

    w->updateSem = NX_SemaphoreCreate(0);
    if (w->updateSem == NX_SOLT_INVALID_VALUE)
    {
        WM_WindowFreeID(w->id);
        NX_ShareMemUnmap(map_solt);
        NX_ShareMemClose(w->vmo);
        NX_FifoDestroy(w->event);
        return NX_ENOMEM;
    }

    w->config.x = x;
    w->config.y = y;
    w->config.width = width;
    w->config.height = height;
    w->config.event_mask = NX_EVENT_MASK_DEFAULT;
    w->bg = bg;
    w->vmo_map = map_solt;
    w->flags = flags;

    NX_ListInit(&w->node);

    NX_SurfaceWrap(w->surface, pixel, width, height);
    NX_SurfaceClear(w->surface, w->bg, 0, 0, width, height);

    if (connect)
    {
        connect->event = NX_SoltCopy(client, w->event);
        connect->vmo = NX_SoltCopy(client, w->vmo);
        connect->surface->width = width;
        connect->surface->height = height;
    }

    return NX_EOK;
}

NX_PRIVATE NX_Error WindowDeinit(WM_WindowType * w)
{
    if (!w)
    {
        return NX_EINVAL;
    }
    NX_FifoDestroy(w->event);
    NX_SemaphoreDestroy(w->updateSem);
    NX_ShareMemUnmap(w->vmo_map);
    NX_ShareMemClose(w->vmo);
    WM_WindowFreeID(w->id);
    return NX_EOK;
}

WM_WindowType * SysWindowCreate(int x, int y, int width, int height, NX_Color bg, NX_U32 flags,
    NX_Solt client, NX_WindowConnect *connect)
{
    WM_WindowType * w;
    NX_Error err;

    if ((flags & NX_WINDOW_FLAG_FULLSCREEN) != 0)
    {
        x = 0;
        y = 0;
        width = WM_GetWidth();
        height = WM_GetHeight();
    }

    if (width <= 0 || height <= 0)
    {
        return NX_NULL;
    }

    w = NX_MemAlloc(sizeof(WM_WindowType));
    if (!w)
    {
        return NX_NULL;
    }
    
    if ((err = WindowInit(w, x, y, width, height, bg, flags, client, connect)) != NX_EOK)
    {
        NX_Printf("init window failed with err %d!", err);
        NX_MemFree(w);
        return NX_NULL;
    }
    
    return w;
}

NX_Error SysWindowDestroy(WM_WindowType * w)
{
    if (!w)
    {
        return NX_EINVAL;
    }

    NX_Region r = {w->config.x, w->config.y, w->surface->width, w->surface->height};

    WM_HideWindow(w);
    WindowDeinit(w);
    NX_MemFree(w);

    /* fucus top window */
    WM_FocusWindowByTop();

    /* update window region */
    WindowMergeUpdateRegion(r, NX_NULL);
    return NX_EOK;
}

NX_Error SysWindowGetConfig(WM_WindowType * w, NX_WindowConfig * config)
{
    if (!w || !config)
    {
        return NX_EINVAL;
    }
    *config = w->config;
    return NX_EOK;
}

NX_Error SysWindowSetEventMask(WM_WindowType * w, NX_U32 mask)
{
    if (!w)
    {
        return NX_EINVAL;
    }
    w->config.event_mask = mask;
    return NX_EOK;
}

NX_Error SysWindowShow(WM_WindowType * w)
{
    if (!w)
    {
        return NX_EINVAL;
    }

    WM_RaiseWindow(w);

    WindowMergeUpdateRegion((NX_Region){w->config.x, w->config.y, w->surface->width, w->surface->height}, w);

    WM_FocusWindow(w);
    return NX_EOK;
}

NX_Error SysWindowHide(WM_WindowType * w)
{
    if (!w)
    {
        return NX_EINVAL;
    }

    WM_HideWindow(w);

    WindowMergeUpdateRegion((NX_Region){w->config.x, w->config.y, w->surface->width, w->surface->height}, NX_NULL);

    WM_FocusWindowByTop();

    return NX_EOK;
}

NX_Error SysWindowUpdate(WM_WindowType * w, NX_Region * region)
{
    NX_Region r1;
    
    if (!w)
    {
        return NX_EINVAL;
    }

    if (!region)
    {
        r1.x = w->config.x;
        r1.y = w->config.y;
        r1.w = w->surface->width;
        r1.h = w->surface->height;
    }
    else
    {
        r1.x = region->x;
        r1.y = region->y;
        r1.w = region->w;
        r1.h = region->h;
    }

    WindowMergeUpdateRegion(r1, w);

    return NX_EOK;
}

NX_Error SysWindowMove(WM_WindowType * w, int dx, int dy)
{
    if (!w)
    {
        return NX_EINVAL;
    }

    if ((w->flags & NX_WINDOW_FLAG_FULLSCREEN_DESKTOP) == NX_WINDOW_FLAG_FULLSCREEN_DESKTOP)
    {
        return NX_EPERM;
    }

    NX_Region r1 = {w->config.x, w->config.y, w->surface->width, w->surface->height};
    NX_Region r2 = {w->config.x + dx, w->config.y + dy, w->surface->width, w->surface->height};
    w->config.x += dx;
    w->config.y += dy;

    NX_Region r3 = NX_RegionMerge(r1, r2);

    return SysWindowUpdate(w, &r3);
}

NX_PRIVATE NX_Error WindowResize(WM_WindowType * w, int width, int height,
    NX_Solt client, NX_WindowConnect *connect)
{
    NX_Color *pixel;
    NX_Solt map_new = NX_SOLT_INVALID_VALUE;
    NX_Solt map_solt = NX_SOLT_INVALID_VALUE;

    if (!w || width <= 0 || height <= 0 || width > WM_WINDOW_MAX_SIZE || height > WM_WINDOW_MAX_SIZE)
    {
        return NX_EINVAL;
    }

    /* create new shm */
    map_new = NX_ShareMemOpen(NX_NULL, width * height * sizeof(NX_Color), NX_SHAREMEM_ANONYMOUS);
    if (map_new == NX_SOLT_INVALID_VALUE)
    {
        NX_Printf("shm open err\n");
        return NX_ENOMEM;
    }

    /* map new shm */
    pixel = (NX_Color *)NX_ShareMemMap(map_new, &map_solt);
    if (!pixel)
    {
        NX_Printf("shm map err\n");
        NX_ShareMemClose(w->vmo);
        return NX_ENOMEM;
    }

    /* destroy old shm */
    NX_ShareMemUnmap(w->vmo_map);
    NX_ShareMemClose(w->vmo);
    
    w->config.width = width;
    w->config.height = height;
    w->vmo = map_new;
    w->vmo_map = map_solt;

    NX_SurfaceWrap(w->surface, pixel, width, height);
    NX_SurfaceClear(w->surface, w->bg, 0, 0, width, height);

    if (connect)
    {
        connect->vmo = NX_SoltCopy(client, w->vmo);
    }

    return NX_EOK;
}

NX_Error SysWindowResize(WM_WindowType * w, int width, int height, NX_Solt client, NX_WindowConnect *connect)
{
    NX_Error err;
    
    if (!w)
    {
        return NX_EINVAL;
    }

    if ((w->flags & NX_WINDOW_FLAG_FULLSCREEN_DESKTOP) == NX_WINDOW_FLAG_FULLSCREEN_DESKTOP)
    {
        return NX_EPERM;
    }
    
    NX_Region r1 = {w->config.x, w->config.y, w->surface->width, w->surface->height};

    err = WindowResize(w, width, height, client, connect);
    if (err != NX_EOK)
    {
        return err;
    }
    NX_Region r2 = {w->config.x, w->config.y, w->surface->width, w->surface->height};

    /* flush old region */
    WindowMergeUpdateRegion(r1, NX_NULL);

    /* flush new region */
    WindowMergeUpdateRegion(r2, w);

    return NX_EOK;
}

NX_Surface * SysWindowGetSurface(WM_WindowType * w)
{
    return w->surface;
}

NX_Error SysVideoGetInfo(NX_VideoInfo * info)
{
    info->width = WM_GetWidth();
    info->height = WM_GetHeight();
    info->phyBaseAddr = WM_DisplayGetPhyBase();
    return NX_EOK;
}

NX_Size WM_Dispatch(NX_IpcMsg *ipc_msg)
{
    NX_Request *fr = (NX_Request *)NX_IpcGetMsgData(ipc_msg);

    // NX_Printf("[wm] req: %d\n", fr->req);

    switch (fr->req)
    {
    case NX_WINDOW_CREATE:
    {
        NX_WINDOW_GET_REQUEST(param, NX_WINDOW_CREATE, fr);
        WM_WindowType * w;
        w = SysWindowCreate(param->x, param->y, param->width, param->height, param->bg, param->flags,
            ipc_msg->clientSlot, &param->connect);
        if (w != NX_NULL) {
            return w->id;
        } else {
            return -1;
        }
    }
    case NX_WINDOW_DESTORY:
    {
        NX_WINDOW_GET_REQUEST(param, NX_WINDOW_DESTORY, fr);
        return SysWindowDestroy(WM_WindowGetByID(param->id));
    }
    case NX_WINDOW_CONFIG_GET:
    {
        NX_WINDOW_GET_REQUEST(param, NX_WINDOW_CONFIG_GET, fr);
        return SysWindowGetConfig(WM_WindowGetByID(param->id), (NX_WindowConfig *)&param->config);
    }
    case NX_WINDOW_EVENT_MASK_SET:
    {
        NX_WINDOW_GET_REQUEST(param, NX_WINDOW_EVENT_MASK_SET, fr);
        return SysWindowSetEventMask(WM_WindowGetByID(param->id), param->event_mask);
    }
    case NX_WINDOW_SHOW:
    {
        NX_WINDOW_GET_REQUEST(param, NX_WINDOW_SHOW, fr);
        return SysWindowShow(WM_WindowGetByID(param->id));
    }
    case NX_WINDOW_HIDE:
    {
        NX_WINDOW_GET_REQUEST(param, NX_WINDOW_HIDE, fr);
        return SysWindowHide(WM_WindowGetByID(param->id));
    }
    case NX_WINDOW_UPDATE:
    {
        NX_WINDOW_GET_REQUEST(param, NX_WINDOW_UPDATE, fr);
        return SysWindowUpdate(WM_WindowGetByID(param->id), NX_NULL);
    }
    case NX_WINDOW_MOVE:
    {
        NX_WINDOW_GET_REQUEST(param, NX_WINDOW_MOVE, fr);
        return SysWindowMove(WM_WindowGetByID(param->id), param->dx, param->dy);
    }
    case NX_WINDOW_RESIZE:
    {
        NX_WINDOW_GET_REQUEST(param, NX_WINDOW_RESIZE, fr);
        return SysWindowResize(WM_WindowGetByID(param->id), param->width, param->height, ipc_msg->clientSlot, &param->connect);
    }
    case NX_VIDEO_GET_INFO:
    {
        NX_WINDOW_GET_REQUEST(param, NX_VIDEO_GET_INFO, fr);
        return SysVideoGetInfo(&param->info);
    }
    default:
        NX_Printf("Exception req:%d\n", fr->req);
        return -1;
    }
    return 0;
}
